Skip to content

unix: Wake sleeping operations immediately on scheduled callbacks.#9

Closed
andrewleech wants to merge 3 commits intoreview/unix-sleep-process-pendingfrom
unix-sleep-process-pending
Closed

unix: Wake sleeping operations immediately on scheduled callbacks.#9
andrewleech wants to merge 3 commits intoreview/unix-sleep-process-pendingfrom
unix-sleep-process-pending

Conversation

@andrewleech
Copy link
Copy Markdown
Owner

Summary

On the unix port, time.sleep() and MICROPY_INTERNAL_WFE() were unresponsive to scheduled callbacks — time.sleep() used select() or sleep() with no wakeup mechanism, and WFE busy-polled with a 500us delay.

This adds a signal-based notification so that when something is scheduled (via MICROPY_SCHED_HOOK_SCHEDULED), a signal is sent to the process. A sig_atomic_t flag is set by both mp_hal_signal_event() and the signal handler. mp_unix_sched_sleep() uses pselect() to atomically unblock the signal and enter the wait, avoiding the TOCTOU race where signals for already-queued callbacks could be consumed before the sleep. The signal is blocked process-wide at init so only the thread inside pselect() can receive it, and pthread_sigmask is used on threaded builds for POSIX correctness.

time.sleep() is reworked to loop over mp_unix_sched_sleep() with elapsed-time tracking, processing pending callbacks each iteration. Negative values now raise ValueError to match CPython. WFE uses the same sleep primitive so it blocks properly and wakes on events rather than spinning.

Uses SIGRTMIN+7 where real-time signals are available, falling back to SIGURG.

Testing

Tested on the unix port (Linux), both standard and coverage variants. Windows codepath (_WIN32) is left unchanged — it keeps the existing select()/delay_us behaviour.

Trade-offs and Alternatives

Could have used a pipe/eventfd self-pipe pattern instead of signals, which would avoid any signal-number collision concerns. Signals are simpler and don't require managing an fd, and the chosen signal numbers avoid known conflicts with GC (SIGRTMIN+5) and thread terminate (SIGRTMIN+6).

The time.sleep() rewrite replaces the MICROPY_SELECT_REMAINING_TIME Linux-specific assumption about select() modifying the timeout struct with explicit mp_hal_ticks_ms() tracking, which is portable. Precision drops from microseconds to milliseconds which is acceptable for time.sleep().

Generative AI

I used generative AI tools when creating this PR, but a human has checked the code and is responsible for the description above.

@andrewleech
Copy link
Copy Markdown
Owner Author

/review

@andrewleech andrewleech reopened this Feb 25, 2026
Copy link
Copy Markdown

@mpy-reviewer mpy-reviewer bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signal-based approach using pselect is the right design here — the atomic unblock-and-wait correctly eliminates the TOCTOU race. A few issues to address before merge.

Comment thread ports/unix/unix_mphal.c
}

void mp_unix_init_sched_signal(void) {
struct sigaction sa;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sa is a stack variable; sa_restorer (and any padding) is uninitialised. Use:

Suggested change
struct sigaction sa;
struct sigaction sa = {0};

Comment thread ports/unix/unix_mphal.c

void mp_unix_deinit_sched_signal(void) {
MP_SIGMASK(SIG_UNBLOCK, &sched_signal_mask, NULL);
signal(MP_SCHED_SIGNAL, SIG_DFL);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use sigaction consistently rather than mixing with signal:

Suggested change
signal(MP_SCHED_SIGNAL, SIG_DFL);
struct sigaction sa = {0};
sa.sa_handler = SIG_DFL;
sigemptyset(&sa.sa_mask);
sigaction(MP_SCHED_SIGNAL, &sa, NULL);

Comment thread ports/unix/mphalport.h
// Note that we don't delay for the full TIMEOUT_MS, as execution
// can't be woken from the delay.
// Wait for an event (scheduled callback) or timeout. A signal from
// mp_hal_signal_event() causes select() to return EINTR, waking the wait.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"select()" → "pselect()".

Comment thread ports/unix/modtime.c Outdated
mp_raise_ValueError(MP_ERROR_TEXT("sleep length must be non-negative"));
}
RAISE_ERRNO(res, errno);
uint64_t total_ms = (uint64_t)(val * MICROPY_FLOAT_CONST(1000.0));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This truncates to milliseconds, so time.sleep(0.0001) becomes a no-op (total_ms=0 → loop exits immediately without sleeping). The old code had microsecond precision. At minimum, round up:

Suggested change
uint64_t total_ms = (uint64_t)(val * MICROPY_FLOAT_CONST(1000.0));
uint64_t total_ms = (uint64_t)MICROPY_FLOAT_C_FUN(ceil)(val * MICROPY_FLOAT_CONST(1000.0));

@github-actions
Copy link
Copy Markdown

github-actions bot commented Feb 25, 2026

Code size report:

Reference:  zephyr/mpconfigport: Remove duplicate builtins.open definition. [1ab9b66]
Comparison: unix: Use sched signal in MICROPY_INTERNAL_WFE. [merge of 4443673]
  mpy-cross:    +0 +0.000% 
   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64:  +827 +0.097% standard[incl +16(data)]
      stm32:    +0 +0.000% PYBV10
      esp32:    +0 +0.000% ESP32_GENERIC
     mimxrt:    +0 +0.000% TEENSY40
        rp2:    +0 +0.000% RPI_PICO_W
       samd:    +0 +0.000% ADAFRUIT_ITSYBITSY_M4_EXPRESS
  qemu rv32:    +0 +0.000% VIRT_RV32

pi-anl added 3 commits March 22, 2026 09:24
Install an empty signal handler (without SA_RESTART) for a dedicated
signal so that select() calls return EINTR when the scheduler queues a
callback. mp_hal_signal_event() sends this signal via kill(getpid()),
and MICROPY_SCHED_HOOK_SCHEDULED calls it from mp_sched_schedule().

This replaces the need for a self-pipe mechanism while remaining
async-signal-safe.

Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
Add a drain loop before the sleep to process any already-pending
callbacks, subtracting the time taken from the requested duration.
The existing EINTR/MICROPY_SELECT_REMAINING_TIME loop handles new
callbacks scheduled during the sleep via the signal mechanism.

Also add ValueError for negative sleep values (matching CPython) and
a test exercising the drain loop and error handling.

Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
Replace the fixed 500us delay in MICROPY_INTERNAL_WFE with
mp_unix_sched_sleep(), which sleeps for the full requested timeout but
wakes immediately on EINTR from the scheduler signal.

Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
@andrewleech andrewleech force-pushed the unix-sleep-process-pending branch from 3bd023c to 4443673 Compare March 21, 2026 22:28
@andrewleech andrewleech deleted the branch review/unix-sleep-process-pending March 21, 2026 22:35
@codecov-commenter
Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (review/unix-sleep-process-pending@1ab9b66). Learn more about missing BASE report.
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@                         Coverage Diff                          @@
##             review/unix-sleep-process-pending       #9   +/-   ##
====================================================================
  Coverage                                     ?   98.42%           
====================================================================
  Files                                        ?      174           
  Lines                                        ?    22339           
  Branches                                     ?        0           
====================================================================
  Hits                                         ?    21988           
  Misses                                       ?      351           
  Partials                                     ?        0           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants